import * as PIXI from "pixi.js";
import {
  getGoalData,
  absolutePath
} from "../../utils/index";
import { PreTreatmentResult, Scope } from "../../typings/traverse";
import {
  disposeInteractive,
  disposeBasic
} from "../Directives/index";
import { Pixijsvue } from "../main";
import { PixijsvueComponentConfig } from "../../typings/main";
import { preTreatment } from "./lib/preTreatment";
import { bindDataToSub, getComponentConfig, initComponentConfig, getListeners } from "./lib/dealWithContainer";
function dealWithContainer(this: Pixijsvue, ele: Element, scope?: Scope) {
  const {$data, $watcher} = this;
  let container: PIXI.Container = new PIXI.Container();
  this.$directives.bind(container, ele, scope);
  let tagName = ele.tagName.toLowerCase();
  let PixijsvueComponentConfig: PixijsvueComponentConfig | undefined;
  // slot 处理
  // component 处理 (局部组件可以覆盖全局组件)
  PixijsvueComponentConfig = getComponentConfig({ components: this.$options.components, _globalComponents: this.$root._globalComponents}, tagName);
  if (PixijsvueComponentConfig) {
    let config = initComponentConfig({_data: this._data},PixijsvueComponentConfig, ele, scope);
    // slot
    let slot: PIXI.Container | undefined;
    if (ele.children.length > 0) {
      slot = new PIXI.Container();
      Object.values(ele.children).forEach((_ele) => {
        slot!.addChild(forEach.call(this, _ele, scope));
      });
      ele.innerHTML = "";
    }
    // listeners
    let listeners = getListeners({ $data: this.$data, $methods: this.$methods}, ele, config.props, scope);
    let pixijsvueComponent = new Pixijsvue(
      config,
    );
    bindDataToSub({$watcher, $data, pixijsvueComponent}, ele, config.props, scope);
    this.$children.push(pixijsvueComponent);
    container = pixijsvueComponent.$vm;
    disposeInteractive(this, this.$data, this.$methods, container, ele, scope, true);
  } else {
    disposeInteractive(this, this.$data, this.$methods, container, ele, scope, false);
  }

  let keyValue: string | number | undefined = undefined;
  if (scope) {
    // key 为item 或item.key
    const { item, items, key } = scope;
    if (key) {
      keyValue = getGoalData(this.$data, absolutePath(items, item, key));
      if (typeof keyValue != "number" && typeof keyValue != "string") {
        throw new Error("v-for中v-key或:key必须为string或number类型");
      }
    }
  }
  let cb = () => {
    disposeBasic(this, {
      $data: this.$data,
      $globalResource: this.$globalResource,
      $id: this.$id,
      $resource: this.$resource,
      $style: this.$style,
      $watcher: this.$watcher,
      _depList: this._depList
    }, container, ele, scope);
  };
  this.$hook.addHooks("mounted", cb);
  return container;
}

/**
 *
 * @param rootContainer 相对root，Container
 * @param subEle root下的子元素
 * @param containerStack
 * @param elementStack
 */
function linkContainer(
  this: Pixijsvue,
  rootContainer: PIXI.Container,
  subEle: Element,
  containerStack: Array<PIXI.Container>,
  elementStack: Array<Element>,
  scope?: Scope
) {
  let { $data, $watcher } = this;
  let depName = "data";
  let preTreatmentResult: PreTreatmentResult = preTreatment(
    { $data },
    subEle,
    scope
  );
  const { element } = preTreatmentResult;

  if (preTreatmentResult.v_for) {
    let containerArray: Array<PIXI.Container> = [];
    let firstChildSit: number;
    let nowSit = 0;
    const { item, items, key } = preTreatmentResult.v_for;
    // length 是需要随着数组的长度发生变化的
    let { length } = preTreatmentResult.v_for;
    for (let i = 0; i < length; i++) {
      /**
       * 由于预处理v-for指令后，得到了，需要循环渲染的Container个数
       * 和每个新Container的特有scope作用域。
       * 所以就以预处理中去除v-for等指令的元素作为根元素，递归的调用forEach。
       *
       */
      let container = forEach.call(this, element, {
        item,
        items: `${items}.${i}` /* 这里加上 .i 目的是其子元素调用getGoalData时，可以准确的获取该scope中的数据 */,
        /**
         * @example
         * 在vue中
         * <template>
         *  <div>
         *    <div v-for="item in items" :key="item">
         *      <div v-text="item"></div>
         *    </div>
         *  </div>
         * </template>
         * v-text="item", 这里的item不是data中的item,而是v-for="item in items",中的item。
         * 同理，`${items}.${i}`中加上i, 就是为了能从items中准确定位到对应的item
         */
        key /* TODO */,
      });
      rootContainer.addChild(container);
      containerStack.push(container);

      // 这里将v-for生成的container，保存起来，当proxy.arr执行sort之类对原数组
      // 有影响的行为时，用来重新渲染所有container
      containerArray.push(container);
    }
    // 记录头一个container的下标，这样无论这组连续的container在哪个位置
    // 删除后，都可以根据这个下标，依次插入container，恢复原来的布局
    firstChildSit = rootContainer.getChildIndex(containerArray[0]);
    nowSit = firstChildSit;
    let fun: Function = () => {
      // 当使用push增加元素时，要重新确认length的值
      length = getGoalData($data, items).length;
      containerArray.forEach((container) => {
        rootContainer.removeChild(container);
      });
      containerArray = [];
      for (let i = 0; i < length; i++) {
        containerArray.push(
          forEach.call(this, element, {
            item,
            items: `${items}.${i}`,
            key,
          })
        );
      }
      containerArray.forEach((container) => {
        rootContainer.addChildAt(container, nowSit);
        nowSit++;
      });
      nowSit = firstChildSit;
    };
    if (!$watcher[items]) {
      $watcher[depName][items] = [];
    }
    $watcher[depName][items].push(fun);
  } else if (preTreatmentResult.v_if) {
    // 记录这个标记v-if的container的位置
    let sit = rootContainer.children.length;
    const { path } = preTreatmentResult.v_if;
    let data = getGoalData(this.$data, path);
    let container: PIXI.Container;
    if (data) {
      elementStack.push(subEle);
      container = dealWithContainer.call(this, subEle, scope);
      rootContainer.addChild(container);
      containerStack.push(container);
    }
    let fun: Function = () => {
      if (getGoalData(this.$data, path) && !data) {
        container = forEach.call(this, element, scope);
        rootContainer.addChildAt(container, sit);
      } else if (!getGoalData(this.$data, path) && data) {
        rootContainer.removeChild(container);
      }
      data = getGoalData(this.$data, path);
    };
    if (!$watcher[path]) {
      $watcher[depName][path] = [];
    }
    $watcher[depName][path].push(fun);
  } else {
    elementStack.push(subEle);
    let container = dealWithContainer.call(this, subEle, scope);
    rootContainer.addChild(container);
    containerStack.push(container);
  }
}
/**
 *
 * @param rootEle template下的第一个元素，（注：和vue一样template的子元素只有一个）
 * @param { Scope } scope 作用域参数
 * 例： "item in items"
 * 如果作用域参数存在，则在作用域内使用item就相当于
 * 使用getGoalData(items)[item];
 */
function forEach(
  this: Pixijsvue,
  rootEle: Element | HTMLElement,
  scope?: Scope
) {
  let stack: Array<Element> = [];
  let containerStack: Array<PIXI.Container> = [];
  let rootContainer = dealWithContainer.call(this, rootEle, scope);

  if (rootEle.children.length == 0) {
    return rootContainer;
  }
  stack.push(rootEle);
  containerStack.push(rootContainer);
  while (stack.length != 0) {
    let item: Element = <Element>stack.pop();
    let containerItem: PIXI.Container = <PIXI.Container>containerStack.pop();
    // 该元素有没有子元素
    if (item.children.length != 0) {
      Object.values(item.children).forEach((ele: Element) => {
        linkContainer.call(
          this,
          containerItem,
          ele,
          containerStack,
          stack,
          scope
        );
      });
    }
  }
  return rootContainer;
}

export { forEach }